﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2012-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI.dll can be used for free under the MulanPSL2 license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: UdpReceiver.cs
* 文件说明: UDP扩展类
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2022-11-01: V1.0.0 增加文件说明
******************************************************************************/

using Sunny.FrameDecoder;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Sunny.Udp
{
    /// <summary>
    /// UDP数据接收类
    /// </summary>
    public class UdpServer : IDisposable
    {
        private System.Net.Sockets.UdpClient _client;
        private readonly Func<BaseByteFrameDecoder> _decoder = null;
        private DateTime _lastCheckTime;
        private Thread _threadReceive;

        private readonly List<IPEndPoint> _clearPoints = new();
        private readonly ConcurrentDictionary<IPEndPoint, DecoderInfo> _decoders = new();

        /// <summary>
        /// 空闲断开时间（未收到Ping或者数据）
        /// </summary>
        public TimeSpan FreeClearTime { get; set; } = TimeSpan.Zero;

        /// <summary>
        /// 接收线程是否开启
        /// </summary>
        public bool IsStart { get; private set; }

        private string name;

        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get => name ?? this.GetType().Name;
            set => name = value;
        }

        private int receiveBufferSize = 65535;

        /// <summary>
        /// 接收缓冲大小
        /// </summary>
        [DefaultValue(65536)]
        public int ReceiveBufferSize
        {
            get => receiveBufferSize;
            set => receiveBufferSize = Math.Max(value, 1024);
        }

        /// <summary>
        /// Udp端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// 通讯套接字
        /// </summary>
        public Socket Server => _client?.Client;

        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// 线程休眠时间
        /// </summary>
        public TimeSpan ThreadSleepTime { get; set; } = TimeSpan.FromMilliseconds(1);

        /// <summary>
        /// 错误回调事件
        /// </summary>
        public event OnUdpSocketError OnError;

        /// <summary>
        /// 服务开启事件
        /// </summary>
        public event EventHandler OnStarted;

        /// <summary>
        /// 服务停止事件
        /// </summary>
        public event EventHandler OnStopped;

        /// <summary>
        /// 接收数据事件
        /// </summary>
        public event OnUdpSpanEventHandler OnReceived;

        /// <summary>
        /// 错误回调
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DoError(Exception e)
        {
            Console.WriteLine(Name + " Error: " + e.Message);
        }

        /// <summary>
        /// 服务开启
        /// </summary>
        protected virtual void DoStarted()
        {
            Console.WriteLine(Name + " Started.");
        }

        /// <summary>
        /// 服务停止
        /// </summary>
        protected virtual void DoStopped()
        {
            Console.WriteLine(Name + " Stopped.");
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DoReceived(UdpSpanDataEventArgs e) { }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">端口</param>
        public UdpServer(int port)
        {
            Port = port;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">端口</param>
        /// <param name="decoder">解码器</param>
        /// <param name="freeClearTime">解码器清理时间间隔</param>
        public UdpServer(int port, Func<BaseByteFrameDecoder> decoder, TimeSpan freeClearTime) : this(port)
        {
            _decoder = decoder;
            FreeClearTime = freeClearTime;
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        public virtual void Dispose()
        {
            Stop();

            foreach (var item in _decoders)
            {
                item.Value.Dispose();
            }

            _decoders.Clear();
        }

        /// <summary>
        /// 开启接收数据线程
        /// </summary>
        public void Start()
        {
            if (IsStart) return;

            try
            {
                _client = new System.Net.Sockets.UdpClient(new IPEndPoint(IPAddress.Any, Port));
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }

            IsStart = true;
            _client.Client.ReceiveBufferSize = receiveBufferSize;
            _threadReceive = new Thread(ReceiveMessages) { IsBackground = true };
            _threadReceive.Start();//开启
            DoStarted();
            OnStarted?.Invoke(this, EventArgs.Empty);
            _lastCheckTime = DateTime.Now;
        }

        /// <summary>
        /// 停止接收数据线程
        /// </summary>
        public void Stop()
        {
            if (!IsStart) return;
            IsStart = false;
            DoStopped();
            OnStopped?.Invoke(this, EventArgs.Empty);
            _client?.Close();
        }

        private void Decoder_OnDecoder(object sender, IByteEventArgs e)
        {
            ReadOnlySpan<byte> span = e.Value.AsSpan();
            var args = new UdpSpanDataEventArgs(e.Decoder.IPEndPoint, e.Decoder, ref span);
            DoReceived(args);
            OnReceived?.Invoke(this, args);
        }

        private void ReceiveMessages()//线程回调函数
        {
            while (IsStart)
            {
                try
                {
                    if (_client.Available > 0 && _client.Client != null)
                    {
                        IPEndPoint endPoint = new(IPAddress.Any, 0);
                        ReadOnlySpan<byte> receiveBytes = _client.Receive(ref endPoint);
                        if (receiveBytes.Length > 0)
                        {
                            if (_decoder == null)
                            {
                                var args = new UdpSpanDataEventArgs(endPoint, null, ref receiveBytes);
                                DoReceived(args);
                                OnReceived?.Invoke(this, args);
                            }
                            else
                            {
                                if (!_decoders.ContainsKey(endPoint))
                                {
                                    var decoder = _decoder.Invoke();
                                    decoder.IPEndPoint = endPoint;
                                    decoder.OnDecoder += Decoder_OnDecoder;
                                    _decoders.TryAdd(endPoint, new DecoderInfo(decoder));
                                }

                                _decoders[endPoint].Decode(receiveBytes);
                            }
                        }
                    }
                    else
                    {
                        Thread.Sleep(ThreadSleepTime);
                    }

                    if (_decoder != null && DateTime.Now - _lastCheckTime > TimeSpan.FromMinutes(1))
                    {
                        _lastCheckTime = DateTime.Now;
                        foreach (var decoder in _decoders)
                        {
                            if (DateTime.Now - decoder.Value.LastDataTime > FreeClearTime)
                            {
                                _clearPoints.Add(decoder.Key);
                            }
                        }

                        foreach (var item in _clearPoints)
                        {
                            _decoders[item].Dispose();
                            _decoders.TryRemove(item, out _);
                        }

                        _clearPoints.Clear();
                    }
                }
                catch (Exception ex)
                {
                    DoError(ex);
                    OnError?.Invoke(this, ex);
                }
            }
        }
    }
}